use async_trait::async_trait;
use log::trace;
use xelis_common::{
    block::TopoHeight,
    serializer::Serializer,
};
use crate::core::{
    error::BlockchainError,
    storage::{
        rocksdb::{
            Asset,
            AssetId,
            Column,
            IteratorMode
        },
        snapshot::Direction,
        RocksStorage,
        VersionedAssetProvider
    }
};

#[async_trait]
impl VersionedAssetProvider for RocksStorage {
    // delete versioned assets at topoheight
    async fn delete_versioned_assets_at_topoheight(&mut self, topoheight: TopoHeight) -> Result<(), BlockchainError> {
        trace!("delete versioned assets at topoheight {}", topoheight);
        let prefix = topoheight.to_be_bytes();

        // Cloned snapshot to iterate without borrowing issues
        let snapshot = self.snapshot.clone();
        for res in Self::iter_raw_internal(&self.db, snapshot.as_ref(), IteratorMode::WithPrefix(&prefix, Direction::Forward), Column::VersionedAssets)? {
            let (versioned_key, value) = res?;

            Self::remove_from_disk_internal(&self.db, self.snapshot.as_mut(), Column::VersionedAssets, &versioned_key)?;

            let key_without_prefix = &versioned_key[8..];
            
            let asset_id = AssetId::from_bytes(&key_without_prefix[0..8])?;
            let asset_hash = self.get_asset_hash_from_id(asset_id)?;
            let mut asset = self.get_asset_type(&asset_hash)?;

            if asset.data_pointer.is_some_and(|pointer| pointer >= topoheight) {
                let prev_topo = Option::from_bytes(&value)?;
                if asset.data_pointer != prev_topo {
                    asset.data_pointer = prev_topo;
                    Self::insert_into_disk_internal(&self.db, self.snapshot.as_mut(), Column::Assets, &asset_hash, &asset)?;
                }
            }
        }

        Ok(())
    }

    // delete versioned assets above topoheight
    async fn delete_versioned_assets_above_topoheight(&mut self, topoheight: TopoHeight) -> Result<(), BlockchainError> {
        trace!("delete versioned assets above topoheight {}", topoheight);
        let start = (topoheight + 1).to_be_bytes();
            let snapshot = self.snapshot.clone();
        for res in Self::iter_raw_internal(&self.db, snapshot.as_ref(), IteratorMode::From(&start, Direction::Forward), Column::VersionedAssets)? {
            let (key, value) = res?;
            // Delete the version we've read
            Self::remove_from_disk_internal(&self.db, self.snapshot.as_mut(), Column::VersionedAssets, &key)?;

            let asset_id = AssetId::from_bytes(&key[8..16])?;
            let hash = self.get_asset_hash_from_id(asset_id)?;
            let mut asset = self.get_asset_type(&hash)?;

            // This algorithm should be finding the latest valid data pointer
            // while limiting updates, it will write the highest
            // data pointer if any, or set to None

            // Case 1: data pointer is above topoheight => we update it
            // Case 2: data pointer is None => we update it
            if asset.data_pointer.is_none_or(|v| v > topoheight) {
                let prev_topo = Option::from_bytes(&value)?;
                // Case 1: prev topo is below or equal to requested topoheight => update it
                // Case 2: prev topo is None but pointer is Some => we update it
                let filtered = prev_topo.filter(|v| *v <= topoheight);
                if filtered != asset.data_pointer {
                    asset.data_pointer = filtered;
                    Self::insert_into_disk_internal(&self.db, self.snapshot.as_mut(), Column::Assets, &hash, &asset)?;
                }
            }
        }

        Ok(())
    }

    // delete versioned assets below topoheight
    async fn delete_versioned_assets_below_topoheight(&mut self, topoheight: TopoHeight, keep_last: bool) -> Result<(), BlockchainError> {
        trace!("delete versioned assets below topoheight {}", topoheight);
        self.delete_versioned_below_topoheight::<Asset, AssetId>(Column::Assets, Column::VersionedAssetsSupply, topoheight, keep_last, |_, v| Ok((v.id, v.data_pointer)))
    }
}